// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"errors"
	"flag"
	"fmt"
	"strings"
)

// -------------------------------------------------------------------

type HexOutput struct {
	buf []byte
}

func (o *HexOutput) Flush() {
	if len(o.buf) > 0 {
		fmt.Printf("%s ", strings.Repeat("   ", cap(o.buf)-len(o.buf)))
		for _, v := range o.buf {
			fmt.Printf("%c", v)
		}
		fmt.Printf("\n")
		o.buf = o.buf[:0]
	}
}

func (o *HexOutput) WriteByte(p Prefix, c byte) {
	if len(o.buf) >= cap(o.buf) {
		o.Flush()
		fmt.Printf("%s", p.String())
	}
	fmt.Printf(" %2.2x", c)
	if c < 32 || 126 < c {
		// anything not ascii will be displayed as "."
		c = '.'
	}
	o.buf = o.buf[:len(o.buf)+1]
	o.buf[len(o.buf)-1] = c
}

// -------------------------------------------------------------------

const (
	needsPrefixAndSpace = iota
	needsSpace
	needsNothing
)

type TextOutput struct {
	needs int
}

func (o *TextOutput) Flush() {
	o.needs = needsSpace
}

func (o *TextOutput) WriteByte(p Prefix, c byte) {
	switch o.needs {
	case needsPrefixAndSpace:
		fmt.Printf("%s ", p.String())
	case needsSpace:
		fmt.Printf(" ")
	}
	o.needs = needsNothing
	fmt.Printf("%c", c)
	if c == '\n' {
		o.needs = needsPrefixAndSpace
	}
}

// -------------------------------------------------------------------

type OutputFmt interface {
	Flush()
	WriteByte(p Prefix, c byte)
}

var (
	_                  = flag.Bool("hex", true, "output hex (default)")
	doTextOutput *bool = flag.Bool("text", false, "output text")
	do9pOutput   *bool = flag.Bool("9p", false, "output 9p")

	maxTextLen *int = flag.Int("cols", 18, "number of hex columns")
)

func GetOutputFmt() OutputFmt {
	switch {
	case *doTextOutput:
		return &TextOutput{}
	case *do9pOutput:
		die(errors.New("9p format is not implemented"))
	default:
		return &HexOutput{make([]byte, *maxTextLen)}
	}
	return nil
}

// -------------------------------------------------------------------

type Output struct {
	PrefixChangedCh chan Prefix
	DataCh          chan []byte
	CommentCh       chan string
}

func (o *Output) Run() {
	of := GetOutputFmt()
	var curp Prefix
	for {
		select {
		case p := <-o.PrefixChangedCh:
			curp = p
			of.Flush()
			fmt.Printf("%s", curp.String())
		case s := <-o.CommentCh:
			fmt.Printf("\n%s %s\n%s\n", curp.String(), s, curp.String())
		case d := <-o.DataCh:
			for _, c := range d {
				of.WriteByte(curp, c)
			}
		}
	}
}

func NewOutput() *Output {
	return &Output{
		PrefixChangedCh: make(chan Prefix),
		DataCh:          make(chan []byte),
		CommentCh:       make(chan string),
	}
}
